/*
 Copyright 2013--Present JMM_PROGNAME
 
 This file is distributed under the terms of the JMM_PROGNAME License.
 
 You should have received a copy of the JMM_PROGNAME License.
 If not, see <JMM_PROGNAME WEBSITE>.
*/
// CREATED    : 10/10/2013
// LAST UPDATE: 8/29/2015

#include "NeuralNet.hpp"

// STL
#include <iostream>


//========================================================================
//========================================================================
//
// NAME: void NEURAL_NET::scale_input(vector<double> & x)
//
// DESC: Scales a given input so that its mean value averaged over the entire set is 0, that it is decorrelated, and the variance is 1
//
// NOTES:
//     ! see the ``Efficient BackProp'' paper by LeCun, it describes the strategy of pre-processing data as:
//          (1) shift the data so that its mean is 0
//          (2) de-correlate the data
//          (3) normalize the data so each input has a variance of ~1
//     ! I do it *slightly* different, by first shifting the data so that it has a mean and standard deviation of 1 and THEN de-correlating the data -- this makes it easier to extract leading eigenvalues, etc.
//
//========================================================================
//========================================================================
void NEURAL_NET::scale_input(std::vector<double> &x)
{
    if( !m_isTrained )
    {
        std::cout << "Error in NEURAL_NET::scale_input(): Network has not been trained -- cannot scale data!" << std::endl;
        exit(0);
    }

    if(this->m_transf_method == 0)
    {
        return;
    }
    
    // FIRST SCALE INPUT DATA TO HAVE 0 MEAN WITH 1 STD. DEV. ...
    for(int i = 0; i < m_ninp; ++i)
    {
        if( m_inp_stddev[i] != 0.0 )
        {
            x[i] = (x[i] - m_inp_mean[i])/m_inp_stddev[i];
        }
        else
        {
            // << jmm: I am not sure how one should safeguard here to prevent dividing by a 0 standard deviation -- I see a couple of ideas:
            //     - as is done here, just subtract to 0 mean
            //     - (probably better) eliminate this node, storing the (fixed) value it should be >>
            x[i] = (x[i] - m_inp_mean[i]);
        }
    }
    
    // NOW TRANSFORM DATA ...
    std::vector<double> b(m_ninp, 0.0);
    std::vector<double> W(m_transf_ninp, 0.0);
    
    switch( m_transf_method )
    {
        case 2:
            
            // PCA DE-CORRELATION & NORMALIZATION TO [-1,1] WITH 1 COVARIANCE, WITH POSSIBLE DATA REDUCTION ...
            for( int j = 0; j < m_ninp; ++j )
            {
                for( int k = 0; k < m_ninp; ++k )
                {
                    b[j] += ( m_transf_matrix[j][k]*x[k] );
                }
            }
            
            for(int j = 0; j < m_ninp; ++j)
            {
                x[j] = b[j];
            }
            
            break;
/*
        case 3:
            
            // KERNAL LDA & NORMALIZATION TO [-1,1] ...            
            // THE PROJECTION OF A NEW PATTERN X ONTO W IS GIVEN BY (W*Phi(X)) = sum_{i=1,l}[alpha_i*k(X_i,X)]
            // ! see Eq. (10) in Mueller
            // ! rememnber that alpha_i is stored in row 0 of the transformation matrix
            
            // PARSE THE INPUT THROUGH THE TRANSFORMATION MATRIX
            for(int j = 0; j < transf_ninp; ++j)
            {
                W[j] = 0.0;
                for(unsigned int i = 0; i < LDA_vector.size(); ++i) { W[j] += transf_matrix[j][i]*GaussianRBF_kernel( LDA_vector[i], x, LDA_sigma); }
                
                // TRANSFORM W TO HAVE A MEAN OF 0 & STD. DEV. OF 1 TOO
                W[j] = (W[j] - LDA_mean)/LDA_stddev;
            }
            
            // ASSIGN TO W to x, STORING ALL VALUES ABOVE transf_ninp WITH 0
            for(int i = 0; i < transf_ninp; ++i)    { x[i] = W[i]; }
            for(int i = transf_ninp; i < ninp; ++i) { x[i] = 0.0; }
            
            break;
*/
        default:
            break;
    }
}


//========================================================================
//========================================================================
//
// NAME: void NEURAL_NET::scale_output(std::vector<double> &x)
//
// DESC: Scales output from a network.
//
// NOTES:
//     ! because just by calling this there *SHOULD* have been a prior call to scale_input(), we don't need to check m_isTrained
//
//========================================================================
//========================================================================
void NEURAL_NET::scale_output(std::vector<double> &x)
{
    if(this->m_transf_method == 0)
    {
        return;
    }
    
    for(int i = 0; i < m_nout; ++i)
    {
        x[i] = x[i]*(m_out_max[i] - m_out_min[i]) + m_out_min[i];
    }
}


//========================================================================
//========================================================================
//
// NAME: void NEURAL_NET::scale_output(vector<double> & x, int direction)
//
// DESC: Scales training set output for presentation *TO* network (e.g., training).
//
// NOTES:
//     ! because just by calling this we *SHOULD* be working with a training set, we don't need to check m_isTrained
//
//========================================================================
//========================================================================
void NEURAL_NET::scale_TS_output(std::vector<double> &x)
{
    if(this->m_transf_method == 0)
    {
        return;
    }
    
    for(int i = 0; i < m_nout; ++i)
    {
        x[i] = (x[i] - m_out_min[i])/(m_out_max[i] - m_out_min[i]);
    }
}






